#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TextStyles.h"

#include "Class.h"
#include "ObjectTable.h"
#include "Math.h"

//---- ParaManager -------------------------------------------------------------

SmartParaStyles gParaStyles;

//NewMetaImpl(ParaStyleManager,OrdCollection, (TP(gParaStyles)));
NewMetaImpl0(ParaStyleManager,OrdCollection);

ParaStyleManager::ParaStyleManager() : OrdCollection(cContainerInitCap)
{
}

ParaStyleManager::~ParaStyleManager()
{
	Iter next(this);
	register ParaStyle *pp;
	while (pp= (ParaStyle*) next()) {
		// sp->Object::~Object();
		ObjectTable::Remove(pp);
		delete (void*) pp;
	}
}

ParaStyle *ParaStyleManager::MakeParaStyle(ParaDesc &p)
{
	Iter next(this);
	ParaStyle *pp;
	while (pp= (ParaStyle*)next()) {
		if (
			(pp->p.alignment == p.alignment)
			&& (pp->p.rightIndent == p.rightIndent)
			&& (pp->p.leftIndent == p.leftIndent)
			&& (pp->p.firstIndent == p.firstIndent)
			&& (pp->p.spacing == p.spacing)
			&& (pp->p.spaceBefore == p.spaceBefore)
			&& (pp->p.spaceAfter == p.spaceAfter)
			&& (pp->p.flags == p.flags)
			&& (pp->p.tabs.IsEqual(p.tabs))
		)
			return pp;
	}
	pp= new ParaStyle(0, p);
	Add(pp);
	return pp;
}

ParaStyle *ParaStyleManager::ChangeProperty(ParaStyle *pp, TxtParaProp mode,
							    const ParaDesc &np)
{
	ParaDesc pd;
	pp->CopyProperties(pd);

	if ((mode & eTxtPAlign) == eTxtPAlign)
		pd.alignment= np.alignment;
	if ((mode & eTxtPLeft) == eTxtPLeft)
		pd.leftIndent= np.leftIndent;
	if ((mode & eTxtPRight) == eTxtPRight)
		pd.rightIndent= np.rightIndent;
	if ((mode & eTxtPFirstIndent) == eTxtPFirstIndent)
		pd.firstIndent= np.firstIndent;
	if ((mode & eTxtPSpacing) == eTxtPSpacing)
		pd.spacing= np.spacing;
	if ((mode & eTxtPSpaceBefore) == eTxtPSpaceBefore)
		pd.spaceBefore= np.spaceBefore;
	if ((mode & eTxtPSpaceAfter) == eTxtPSpaceAfter)
		pd.spaceAfter= np.spaceAfter;
	if ((mode & eTxtPAddTab) == eTxtPAddTab)
		pd.tabs.Add(np.tabs.stops[0].x, eTxtTLeft);
	if ((mode & eTxtPAddTabR) == eTxtPAddTabR)
		pd.tabs.Add(np.tabs.stops[0].x, eTxtTRight);
	if ((mode & eTxtPAddTabC) == eTxtPAddTabC)
		pd.tabs.Add(np.tabs.stops[0].x, eTxtTCenter);
	if ((mode & eTxtPAddTabD) == eTxtPAddTabD)
		pd.tabs.Add(np.tabs.stops[0].x, eTxtTDecimal);
	if ((mode & eTxtPRemoveTab) == eTxtPRemoveTab)
		pd.tabs.Remove(np.tabs.stops[0].x);
	if ((mode & eTxtPRemoveTab) == eTxtPRemoveTab)
		pd.tabs.Remove(np.tabs.stops[0].x);
	if ((mode & eTxtPReplaceTabs) == eTxtPReplaceTabs)
		pd.tabs= np.tabs;
	if ((mode & eTxtPNoBreak) == eTxtPNoBreak) {
		if (TESTBIT(np.flags, (int)eTxtPFlagNoBreak))
			SETBIT(pd.flags, (int)eTxtPFlagNoBreak);
		else
			CLRBIT(pd.flags, (int)eTxtPFlagNoBreak);
	}
	if ((mode & eTxtPKeepNext) == eTxtPKeepNext) {
		if (TESTBIT(np.flags, (int)eTxtPFlagKeep))
			SETBIT(pd.flags, (int)eTxtPFlagKeep);
		else
			CLRBIT(pd.flags, (int)eTxtPFlagKeep);
	}
	return MakeParaStyle(pd);
}

//---- SmartParaStyles ---------------------------------------------------------

ParaStyleManager *SmartParaStyles::make()
{
	if (pm == 0)
		pm= new ParaStyleManager;
	return pm;
}

//---- ParaDesc ----------------------------------------------------------------

void ParaDesc::SetProperty(TxtParaProp what, int value)
{
	switch (what) {
	case eTxtPAlign:
		alignment= (TxtParaAlign)value;
		break;
	case eTxtPLeft:
		leftIndent= value;
		break;
	case eTxtPRight:
		rightIndent= value;
		break;
	case eTxtPFirstIndent:
		firstIndent= value;
		break;
	case eTxtPSpacing:
		spacing= value;
		break;
	case eTxtPSpaceBefore:
		spaceBefore= value;
		break;
	case eTxtPSpaceAfter:
		spaceAfter= value;
		break;
	case eTxtPAddTab:
		tabs.Add(value, eTxtTLeft);
		break;
	case eTxtPAddTabR:
		tabs.Add(value, eTxtTRight);
		break;
	case eTxtPAddTabC:
		tabs.Add(value, eTxtTCenter);
		break;
	case eTxtPAddTabD:
		tabs.Add(value, eTxtTDecimal);
		break;
	case eTxtPEmptyTabs:
		tabs.ntabs= 0;
		break;
	case eTxtPNoBreak:
		if (value)
			SETBIT(flags, (int)eTxtPFlagNoBreak);
		else
			CLRBIT(flags, (int)eTxtPFlagNoBreak);
		break;
	case eTxtPKeepNext:
		if (value)
			SETBIT(flags, (int)eTxtPFlagKeep);
		else
			CLRBIT(flags, (int)eTxtPFlagKeep);
		break;
	default:
		break;
	}
}

bool ParaDesc::TestFlag(TxtParaFlags f)
{
	return TESTBIT(flags, (int)f);
}

//---- ParaStyle ---------------------------------------------------------------

NewMetaImpl(ParaStyle,Object, (T(p.rightIndent), T(p.leftIndent),
					T(p.firstIndent), T(p.spacing),
					T(p.spaceBefore), T(p.spaceAfter), TX(p.flags)));

ParaStyle::ParaStyle(int, ParaDesc &pp)
{
	p= pp;
}

ParaStyle* new_ParaStyle(TxtParaAlign a, int ri, int li, int fi,
								int sp, int sb, int sa, int f, ParaTabs *tabs)
{
	ParaDesc p;
	p.flags= f;
	p.alignment= a;
	p.rightIndent= ri;
	p.leftIndent= li;
	p.firstIndent= fi;
	p.spacing= Math::Max(0, sp);
	p.spaceBefore= Math::Max(0, sb);
	p.spaceAfter= Math::Max(0, sa);
	if (tabs)
		p.tabs.Copy(*tabs);
	return gParaStyles->MakeParaStyle(p);
}

ParaStyle* new_ParaStyle(ParaDesc &pp)
{
	return gParaStyles->MakeParaStyle(pp);
}

ParaStyle::~ParaStyle()
{
	Error("~ParaStyle", "may not delete parastyles");
}

bool ParaStyle::IsEqual(Object *op)
{
	return op == this;
}

OStream &ParaStyle::PrintOn(OStream &s)
{
	return s << (int)p.alignment SP
			<< p.rightIndent SP << p.leftIndent SP << p.firstIndent SP
			<< p.spacing SP << p.spaceBefore SP << p.spaceAfter SP
			<< p.tabs SP << p.flags NL;
}

Object *ParaStyle::ReadAndMap(IStream &s)
{
	ParaDesc pp;
	s >> Enum(pp.alignment)
	>> pp.rightIndent >> pp.leftIndent >> pp.firstIndent
	>> pp.spacing >> pp.spaceBefore >> pp.spaceAfter >> pp.tabs >> pp.flags;
	return new_ParaStyle(
		pp.alignment, pp.rightIndent, pp.leftIndent,
		pp.firstIndent, pp.spacing, pp.spaceBefore, pp.spaceAfter,
		pp.flags, &pp.tabs
	);
}

Object *ParaStyle::deepclone()
{
	return this;
}

void ParaStyle::CopyProperties(ParaDesc &pp)
{
	pp= p;
}
								
const ParaTabs &ParaStyle::GetTabs()
{
	return p.tabs;
}

int ParaStyle::GetProperty(TxtParaProp what)
{
	int val;
	switch (what) {
	case eTxtPAlign:
		val= (TxtParaAlign)p.alignment;
		break;
	case eTxtPLeft:
		val= p.leftIndent;
		break;
	case eTxtPRight:
		val= p.rightIndent;
		break;
	case eTxtPFirstIndent:
		val= p.firstIndent;
		break;
	case eTxtPSpacing:
		val= p.spacing;
		break;
	case eTxtPSpaceBefore:
		val= p.spaceBefore;
		break;
	case eTxtPSpaceAfter:
		val= p.spaceAfter;
		break;
	case eTxtPNoBreak:
		val= TESTBIT(p.flags, (int)eTxtPFlagNoBreak);
		break;
	case eTxtPKeepNext:
		val= TESTBIT(p.flags, (int)eTxtPFlagKeep);
		break;
	default:
		val= -1;
	}
	return val;
}

//---- ParaTabs ----------------------------------------------------------------

const int cTabCapacity= 6;

ParaTabs::ParaTabs()
{
	size= 0;
	ntabs= 0;
	stops= 0;
}

ParaTabs::ParaTabs(const ParaTabs &t)
{
	Copy(t);
}

ParaTabs::~ParaTabs()
{
	if (size != 0)
		delete stops;
}

void ParaTabs::operator= (const ParaTabs &pt)
{
	if (&pt == this)
		return;
	SafeDelete(stops);
	Copy(pt);
}

void ParaTabs::Copy(const ParaTabs &t)
{
	ntabs= t.ntabs;
	if (ntabs == 0) {
		stops= 0;
		size= 0;
	} else {
		size= t.size;
		stops= new TabStop[size];
		for(int i= 0; i < ntabs; i++)
			stops[i]= t.stops[i];
	}
}

bool ParaTabs::IsEqual(ParaTabs &t)
{
	if (t.ntabs != ntabs)
		return FALSE;
	for (int i= 0; i < ntabs; i++)
		if (stops[i].kind != t.stops[i].kind || stops[i].x != t.stops[i].x)
			return FALSE;
	return TRUE;
}

void ParaTabs::Add(int x, TxtTabStop kind)
{
	for (int i= 0; i < ntabs; i++) {
		if(stops[i].x == x) {
			stops[i].kind= kind;
			return;
		}
		else if (stops[i].x > x)
			break;
	}
	if (size == ntabs) {
		if (size == 0)
			stops= new TabStop[size= cTabCapacity];
		else {
			stops= (TabStop*)Storage::ReAlloc(stops, 2*size*sizeof(TabStop));
			size= 2*size;
		}
	}
	for(int j= ntabs-1; j >= i; j--)
		stops[j+1]= stops[j];
	stops[i].x= x;
	stops[i].kind= kind;
	ntabs++;
}

void ParaTabs::Remove(int ix)
{
	if (ix >= ntabs)
		return;
	for (int j= ix; j < size-1; j++)
		stops[j]= stops[j+1];
	ntabs--;
}

void ParaTabs::Move(int ix, int dx)
{
	if (ix >= ntabs)
		return;
	TabStop t;
	t.x= stops[ix].x + dx;
	t.kind= stops[ix].kind;
	Remove(ix);
	Add(t.x, t.kind);
}

OStream& operator<< (OStream &s, ParaTabs &p)
{
	s << p.ntabs SP;
	for (int i= 0; i < p.ntabs; i++)
		s << p.stops[i].x SP << (int)p.stops[i].kind SP;
	return s;
}

IStream& operator>> (IStream &s, ParaTabs &p)
{
	s >> p.ntabs;
	p.stops= new TabStop[p.size= Math::Max(p.ntabs, cTabCapacity)];
	for (int i= 0; i < p.ntabs; i++)
		s >> p.stops[i].x >> Enum(p.stops[i].kind);
	return s;
}

//---- CharStyleSpec -----------------------------------------------------------

CharStyleSpec::CharStyleSpec()
{
	font= eFontHelvetica;
	face= eFacePlain;
	size= 12;
	ink= ePatBlack;
	xor= TRUE;
}

CharStyleSpec::CharStyleSpec(Font *fp, Ink *c, bool mode)
{
	font= fp->Fid();
	face= fp->Face();
	size= fp->Size();
	ink= c;
	xor= mode;
}

CharStyleSpec::CharStyleSpec(GrFont ft, GrFace fc, int sz, Ink *c, bool mode)
{
	font= ft;
	face= fc;
	size= sz;
	ink= c;
	xor= mode;
}

//---- CharDesc ----------------------------------------------------------------

void CharDesc::GetFontProp(GrFont &fid, GrFace &face, int &size)
{
	fid= fp->Fid();
	face= fp->Face();
	size= fp->Size();
}

//---- CharStyleManager --------------------------------------------------------

SmartCharStyles gCharStyles;

//NewMetaImpl(CharStyleManager,OrdCollection, (TP(gCharStyles)));
NewMetaImpl0(CharStyleManager,OrdCollection);

CharStyleManager::CharStyleManager() : OrdCollection(cContainerInitCap)
{
}

CharStyleManager::~CharStyleManager()
{
	Iter next(this);
	register CharStyle *sp;
	while (sp= (CharStyle*) next()) {
		// sp->Object::~Object();
		ObjectTable::Remove(sp);
		delete (void*) sp;
	}
}

CharStyle *CharStyleManager::MakeCharStyle(CharDesc &cc)
{
	Iter next(this);
	register CharStyle *sp;
	while (sp= (CharStyle*)next()) {
		if (
			sp->c.fp == cc.fp &&
			sp->c.ink == cc.ink
		)
			return sp;
	}
	sp= new CharStyle(0, cc);
	Add(sp);
	return sp;
}

CharStyle *CharStyleManager::ChangeProperty(CharStyle *cs, TxtCharProp mode,
															CharStyleSpec st)
{
	CharDesc cc;
	GrFont fid;
	GrFace face;
	int    fsize;

	cs->GetProperties(cc);
	cc.GetFontProp(fid, face, fsize);
	if ((mode & eTxTPFont) == eTxTPFont)
		fid= st.font;
	if ((mode & eTxTPFace) == eTxTPFace)
		if (st.face == eFacePlain)
			face= st.face;
		else {
			if (st.xor == cStyleXorMode)
				face= (GrFace)(face ^ st.face);
			else
				face= (GrFace)(face | st.face);
		}
	if ((mode & eTxtPSize) == eTxtPSize)
		fsize= st.size;
	if ((mode & eTxtPAddSize) == eTxtPAddSize)
		fsize+= st.size;
	if ((mode & eTxtPInk) == eTxtPInk)
		cc.ink= st.ink;
	cc.fp= new_Font(fid, fsize, face);
	return MakeCharStyle(cc);
}

CharStyle *CharStyleManager::Default()
{
	return new_CharStyle(gSysFont, ePatBlack);
}

//---- SmartCharStyles ---------------------------------------------------------

CharStyleManager *SmartCharStyles::make()
{
	if (cm)
		return cm;

	cm= new CharStyleManager;
	return cm;
}

//---- CharStyle ---------------------------------------------------------------

NewMetaImpl(CharStyle,Object, (TP(c.fp), TP(c.ink)));

CharStyle::CharStyle(int, CharDesc &cc)
{
	c= cc;
}

CharStyle *new_CharStyle(Font *f, Ink *c)
{
	CharDesc cc;
	cc.fp= f;
	cc.ink= c;
	return gCharStyles->MakeCharStyle(cc);
}

CharStyle *new_CharStyle(GrFont ft, GrFace fc, int size, Ink *ink)
{
	CharDesc cc;
	cc.fp= new_Font(ft, size, fc);
	cc.ink= ink;
	return gCharStyles->MakeCharStyle(cc);
}

CharStyle::~CharStyle()
{
	Error("~CharStyle", "may not delete charstyles");
}

OStream &CharStyle::PrintOn(OStream &s)
{
	return s << c.fp SP << c.ink NL;
}

Object *CharStyle::ReadAndMap(IStream &s)
{
	CharDesc cc;
	s >> cc.fp >> cc.ink;
	return new_CharStyle(cc.fp, cc.ink);
}

bool CharStyle::IsEqual(Object *op)
{
	return op == this;
}

void CharStyle::GetProperties(CharDesc &cc)
{
	cc.fp= c.fp;
	cc.ink= c.ink;
}

Object *CharStyle::deepclone()
{
	return this;
}

